# -*- encoding: utf-8 -*-

# Copyright 2010 István Szentandrási
# <etude-music-player@googlegroups.com>

# This file is part of Etude.
#
# Etude is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as 
# published by the Free Software Foundation, either version 3 
# of the License, or (at your option) any later version.
#
# Etude is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Etude. If not, see <http://www.gnu.org/licenses/>.

"""
Collection Editor module
"""
from . import _

import gtk

HAVE_GOO = True
try:
    import goocanvas as goo
except ImportError:
    HAVE_GOO = False


import pango
import xmmsclient

from etude.client import Collection as ec
import etude.gui as eg
import etude.img as ei


def load_svg_from_egg(path, size = None):
    return ei.load_svg_from_egg(__package__, path, size)

class HelpFrame(gtk.Frame):
    """
    Frame showed if nothing is selected
    """

    def __init__(self):
        gtk.Frame.__init__(self, "Help")
        tb = gtk.TextBuffer()
        tb.set_text(_("""With CTRL+scroll you can zoom in and out.

You can select and move the operators around the area with LEFT MOUSE BUTTON.

To add new operators drag one of the operator icons from the toolbar and drop them on the canvas.

With the RIGHT MOUSE BUTTON you can create connections between operators. Click 
on an operator with the right mouse button, keep the mouse button pushed while moving over the other connection.
NOTE:
    You cannot always connect operators. Some operators have limited number of operands or none at all. 

To change the name of the collection click on the root item (the blue one without dashes) and you can set the name in the details panel.

To remove connections and operators use SHIFT + LEFT MOUSE BUTTON."""))
        tv = gtk.TextView(tb)
        tv.set_property('editable', False)
        tv.set_wrap_mode( gtk.WRAP_WORD_CHAR)
        self.add(tv)
        self.show_all()

class IDListView(eg.CollectionView):

    """
    Modified version of Collection, which doesn't manipulate data stored 
    on the daemon side
    """
    
    def set_coll(self, coll, order = None):
        """
        set the collection - order should be a list of properties
        """
        self.coll_name = ''
        self.namespace = "Playlists"
        if order != None:
            self.default_order = order
        self._get_coll(coll)
        self._register_cbs(False, False)
        self.connect('key_press_event', self.on_list_button_press)

    def on_list_button_press(self, widget, event, data = None):
        """
        add support fort deleting songs
        """
        if event.keyval == gtk.keysyms.Delete:
            poss = self.get_selected_positions()
            hash_info = {u'type': xmmsclient.PLAYLIST_CHANGED_REMOVE,
                    u'name': ''}
            for index in xrange(len(poss)):
                hash_info[u'position'] = poss[index] - index
                self.playlist_changed_cb(self.client, hash_info)

        return False

    def enable_dnd(self, dest = False, src = False):
        """
        enable drag and drop
        """
        if dest:
            self.enable_model_drag_dest(self.TARGETS[:2], 
                    gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
        else:
            self.unset_rows_drag_dest()
        if src:
            self.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
                    self.TARGETS[:1], #we enable just the moving part
                    gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY)
        else:
            self.unset_rows_drag_source()

    def on_drag_data_recieved(self, widget, drag_context, x, y, 
            selection, info, timestamp):
        """
        changes - remove playlist and coll_name check, and don't communicate with the server
        instead make the changes in place
        """
        model = self.list_model
        drop_info = self.get_dest_row_at_pos(x, y)
        data = selection.data
        if data == None:
            try:
                path, position = drop_info
            except TypeError:
                path = (self.list_model.iter_n_children(None)-1,)
                position = None
            new_pos = path[0]
            tsl = self.get_selection()
            model, path_list = tsl.get_selected_rows()
            old_positions = [p[0] for p in path_list]
            old_less = [p for p in old_positions if p < new_pos]
            old_more = [p for p in old_positions if p >= new_pos]
            old_decr = 0
            if (position == gtk.TREE_VIEW_DROP_BEFORE) and old_less:
                new_pos -= 1
            for old_pos in old_less:
                hash_info = { u'type': xmmsclient.PLAYLIST_CHANGED_MOVE, 
                        u'newposition': new_pos,
                        u'position': old_pos - old_decr,
                        u'id': self.ids[old_pos-old_decr],
                        u'name': ''
                        }
                #move old_pos - old_decr, new_pos
                self.playlist_changed_cb(self.client, hash_info)
                if old_pos < new_pos:
                    old_decr += 1
            if position == gtk.TREE_VIEW_DROP_AFTER:
                new_pos += 1
            for old_pos in old_more:
                hash_info = { u'type': xmmsclient.PLAYLIST_CHANGED_MOVE, 
                        u'newposition': new_pos,
                        u'position': old_pos,
                        u'id': self.ids[old_pos],
                        u'name': ''
                        }
                #move old_pos, new_pos
                self.playlist_changed_cb(self.client, hash_info)
                new_pos += 1
        elif info == 1: #ids
            ids = []
            for song_id in data.strip().split():
                try:
                    ids.append(int(song_id))
                except ValueError:
                    pass
            try:
                path, position = drop_info
            except TypeError:
                path = (self.list_model.iter_n_children(None),)
                position = None
            new_pos = path[0]
            if position in (gtk.TREE_VIEW_DROP_INTO_OR_AFTER, 
                    gtk.TREE_VIEW_DROP_AFTER):
                new_pos += 1
            for song_id in ids:
                hash_info = { u'type': xmmsclient.PLAYLIST_CHANGED_INSERT,
                        u'position': new_pos,
                        u'id': song_id,
                        u'name': ''
                        }
                self.playlist_changed_cb(self.client, hash_info)
                #insert new_pos, id
                new_pos += 1

if HAVE_GOO:
    from collection_types import *

class CollectionEditor(gtk.Dialog):

    """
    Collection editor dialog

    uses goocanvas for collection editing
    """
    def __init__(self, coll_name, namespace, collection, plugin, parent = None):
        
        if HAVE_GOO == False:
            gtk.Dialog.__init__(self, 
                    title = (_("Collection Editor")+" - "+
                        _(" missing pygoocanvas")),
                    parent = parent, 
                    flags = gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
                    buttons = (gtk.STOCK_OK, gtk.RESPONSE_CLOSE))
            self.vbox.add(gtk.Label(
                _("Please install pygoocanvas to use collection editor.")
                ))
            self.show_all()
            return
        gtk.Dialog.__init__(self, title = _("Collection Editor"),
                parent = parent, flags = gtk.DIALOG_DESTROY_WITH_PARENT,
                buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
                    gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        self.set_modal(False)
        self.set_transient_for(None)
        self.set_default_size(720, 480)
        self.set_icon(parent.get_icon())
        self.plugin = plugin

        self.collection = collection
        
        #arrows represent collections on the canvas
        self.arrows = {}
        canvas = goo.Canvas()
        canvas.set_bounds( 0, 0, 100, 100)
        canvas.set_property('automatic-bounds', True)
        canvas.set_property('bounds-from-origin', True)


        #enable dragging collection types into the canvas
        targets = [('COLLECTION_EDITOR_TYPE', gtk.TARGET_SAME_APP, 0),]
        canvas.drag_dest_set( 
                gtk.DEST_DEFAULT_MOTION|gtk.DEST_DEFAULT_HIGHLIGHT|
                gtk.DEST_DEFAULT_DROP, targets, 
                gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY)
        canvas.connect('drag-data-received', self.on_drop_coll_type)
        
        self.scrolled_win = gtk.ScrolledWindow()
        self.scrolled_win.add(canvas)
        self.scrolled_win.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        self.scrolled_win.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)

        hpaned = gtk.HPaned()
        self.vbox.pack_start(hpaned, True, True)

        hpaned.pack2(self.scrolled_win, resize = True, shrink = False)
        
        self.notebook = gtk.Notebook()
        self.notebook.set_tab_pos(gtk.POS_TOP)
        hpaned.pack1(self.notebook, resize = False, shrink = True)

        info_vbox = gtk.VBox()
        self.notebook.append_page(info_vbox, tab_label=gtk.Label(_("Current")))
        self.active_label = gtk.Label("")
        self.active_label.set_line_wrap(False)
        self.active_label.set_ellipsize( pango.ELLIPSIZE_END)
        info_vbox.pack_start(self.active_label, False, False, 5)

        self.helpframe = HelpFrame()
        self.detail_scroll = gtk.ScrolledWindow()
        self.detail_scroll.set_size_request(200, -1)
        self.detail_scroll.set_shadow_type(gtk.SHADOW_NONE)
        self.detail_scroll.set_policy(gtk.POLICY_AUTOMATIC, 
                gtk.POLICY_AUTOMATIC)
        self.detail_vp = gtk.Viewport()
        self.detail_vp.set_shadow_type(gtk.SHADOW_NONE)
        self.detail_viewport = gtk.Alignment(0.0, 0.0, 1.0, 0.0)
        self.detail_viewport.add(self.helpframe)
        self.detail_vp.add(self.detail_viewport)
        self.detail_scroll.add(self.detail_vp)
        info_vbox.pack_start(self.detail_scroll, True, True)
        
        toolbox = gtk.VBox()
        ops = ( ( _('Reference operators'), ('universe', 'reference')),
                ( _('Set operators'), ('union', 'intersection', 'complement')),
                ( _('Filter operators'), ('equals', 'match', 'has', 
                    'greater', 'smaller')),
                ( _('List operators'), ('idlist', 'queue', 'partyshuffle'))
                )
        for type_name, operators in ops:
            exp = gtk.Expander(type_name)
            rows = int(round(len(operators)/3.0))
            table = gtk.Table(rows = rows, columns = 3)
            ind = 0

            for name in operators:
                img = gtk.Image()
                img.set_from_pixbuf(load_svg_from_egg('coll_type/'+name+'.svg',
                    size = (32,32)))
                img.set_tooltip_text(name.capitalize())

                img_button = gtk.Button()
                img_button.set_image(img)
                img_button.set_data('coll_type', name)
                img_button.drag_source_set(gtk.gdk.BUTTON1_MASK, targets, 
                        gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY)
                img_button.connect('drag_data_get', 
                        self.on_drag_get_coll_type_img)
                img_button.connect('drag_begin', self.on_drag_begin_coll_type)
                row = ind/3
                column = ind % 3
                table.attach(img_button, column, column+1, row, row+1, 
                        xoptions=0, yoptions=0)
                ind += 1
            exp.add(table)
            exp.set_expanded(True)
            toolbox.pack_start(exp, False, False)
        
        #for zooming in the canvas
        zoom_box = gtk.HBox()
        self.zoomspin = gtk.SpinButton(climb_rate = 1.0, digits = 2)
        self.zoomspin.set_increments(0.1, 1.0)
        self.zoomspin.set_range(0.1, 10.0)
        self.zoomspin.set_value(1.0)
        self.zoomspin.connect('changed', self.on_zoomspin_change, canvas)
        zoom_box.pack_start(gtk.Label(_("Zoom")+": "), False, False)
        zoom_box.pack_start(self.zoomspin, False, False)
        toolbox.pack_end(zoom_box, False, False)

        self.notebook.prepend_page(toolbox, tab_label = gtk.Label(_("Tools")))

        self.show_all()
        
        self.coll_name = coll_name
        self.namespace = namespace
 
        #stores the current focused operator on the canvas
        self.__focused = None
        self.root = canvas.get_root_item()
        self.setup_canvas(self.root)

        canvas.connect('scroll-event', self.on_change_scale)

    def parse_collection(self):
        """
        Converts the tree structure to an xmms Collection type
        Returns a tuple:
        if the parsing was successful: (collection, new_name)
        else: (None, ( error_message, wrong_item))
        """
        if len(self.treeroot.get_name()) == 0:
            return (None, (_("Please set a name for the collection"), 
                self.treeroot))
        try:
            coll_item = self.treeroot.op_operands[0]
        except IndexError:
            return (None, (_("You should connect an operator to the root item (the blue one without dashes)"), self.treeroot))
        collection, data = self.parse_item(coll_item)
        if collection == None:
            return (None, (data, coll_item))
        stack = []
        #stack structure: (child, parent)
        [stack.append((c, collection)) for c in data]
        while len(stack) != 0:
            coll_item, parent = stack.pop()
            coll, data = self.parse_item(coll_item)
            if coll == None:
                return (None, (data, coll_item))
            parent.operands.append(coll)
            #push the child operands to the stack
            [stack.append( (c, coll) ) for c in data]
        return (collection, self.treeroot.get_name())

    def parse_item(self, coll_item):
        """
        parses an operator and returns an appropriate xmms2 collection or 
        return None with an error message
        """

        """first part - check for attribute and operand errors, and create the
        xmms2 collection"""
        if coll_item.op_type in (ec.Universe, ec.IDList, ec.Queue):
            coll = coll_item.op_type()
        
        elif coll_item.op_type in (ec.Intersection, ec.Union):
            if len(coll_item.op_operands) == 0:
                return (None, _("AND and OR (Intersection and Union) operators need at least one operand"))
            coll = coll_item.op_type()
        
        elif coll_item.op_type in (ec.Smaller, ec.Greater, ec.Match, ec.Equals):
            coll = coll_item.op_type()
            if len(coll_item.op_operands) > 0:
                """remove Universe, which was added automatically, 
                if collection has other operands"""
                coll.operands.remove(coll.operands[0]) 
        
        elif coll_item.op_type == ec.Reference:
            if len(coll_item.op_attributes['reference']) == 0:
                return (None, _("Incorrect value for Reference operator"))
            coll = ec.Reference(coll_item.op_attributes['reference'])
        
        elif coll_item.op_type == ec.PShuffle:
            if len(coll_item.op_operands) == 0:
                return(None, _("Party Shuffle operator requires exactly one operand"))
            t = ec.IDList()
            coll = ec.PShuffle(t)
            coll.operands.remove(t)
        
        elif coll_item.op_type == ec.Complement:
            if len(coll_item.op_operands) == 0:
                return(None, _("Complement (NOT) operator requires exactly one operand"))
            t = ec.IDList()
            coll = ec.Complement(t)
            coll.operands.remove(t)
        
        elif coll_item.op_type == ec.Has:
            if len(coll_item.op_operands) == 0:
                return(None, _("Has operator requires exactly one operand"))
            if len(coll_item.op_attributes['field']) == 0:
                return(None, _("Incorrect value set for field attribute in Has operator"))
            t = ec.IDList()
            coll = ec.Has(t, coll_item.op_attributes['field'])
            coll.operands.remove(t)
        else:
            print(coll_item)
        
        #set ids for medialists
        if coll_item.op_type in (ec.IDList, ec.Queue, ec.PShuffle):
            [coll.ids.append(song_id) for song_id in coll_item.op_ids]

        #set attributes for the collection
        for key, value in coll_item.op_attributes.items():
            coll.attributes[key] = str(value)

        return(coll, coll_item.op_operands)
        

    def on_zoomspin_change(self, spin, canvas):
        """
        the zoom spin changed
        """
        val = self.zoomspin.get_value()
        if val != canvas.get_scale():
            canvas.set_scale(val)

    def on_change_scale(self, canvas, event):
        """
        enables zooming with mouse scroll and Contol pressed down
        """
        if event.state & gtk.gdk.CONTROL_MASK:
            sc = canvas.get_scale()
            if event.direction == gtk.gdk.SCROLL_UP:
                sc *= 1.1
            elif event.direction == gtk.gdk.SCROLL_DOWN:
                sc /= 1.1
            else:
                return False
            self.zoomspin.set_value(sc)
            return True
        return False

    def on_drag_begin_coll_type(self, img_button, drag_context):
        """set the drag icon to the collection icon"""
        img_button.drag_source_set_icon_pixbuf(
                img_button.get_image().get_pixbuf())

    def on_drag_get_coll_type_img(self, img_button, context, selection, 
            target_id, timestamp):
        name = img_button.get_data('coll_type')
        selection.set(selection.target, 8, name)

    def on_drop_coll_type(self, canvas, drag_context, x, y, selection, 
            info, timestamp):
        name = selection.data
        type_dict = { 'universe': ec.Universe,
                'reference': ec.Reference,
                'union': ec.Union,
                'intersection': ec.Intersection,
                'complement': ec.Complement,
                'equals': ec.Equals,
                'match': ec.Match,
                'has': ec.Has,
                'greater': ec.Greater,
                'smaller': ec.Smaller,
                'idlist': ec.IDList,
                'queue': ec.Queue,
                'partyshuffle': ec.PShuffle }
        hvalue = self.scrolled_win.get_hadjustment().get_value()
        vvalue = self.scrolled_win.get_vadjustment().get_value()
        xpos, ypos = canvas.convert_from_pixels(x+hvalue, y+vvalue)
        #xpos,ypos = canvas.convert_to_item_space(self.root, xpos, ypos)
        if name in ('universe', 'union', 'intersection', 'equals',
                'match', 'greater', 'smaller', 'idlist', 'queue'):
            coll = type_dict[name]()
        elif name == 'reference':
            coll = ec.Reference('')
        elif name == 'partyshuffle':
            coll = ec.PShuffle(ec.IDList())
        elif name == 'complement':
            coll = ec.Complement(ec.IDList())
        elif name == 'has':
            coll = ec.Has(ec.IDList(), '')
        else:
            return False
        self.draw_coll(coll, self.root, xpos, ypos)


    def setup_canvas(self, root):
        """
        Create the canvas and the structure of the collection
        using stack again
        """
        self.treeroot = RootGroup(self, root, self.coll_name, x = 5, y = 5)
        xpos = 5
        if self.collection:
            stack = [(self.collection, self.treeroot)]
        else:
            stack = []
        #stack - (child, parent)
        while True:
            try:
                coll, parent = stack.pop()
            except IndexError:
                break
            ypos = parent.get_bounds().y2 + 50
            dc = self.draw_coll(coll, root, xpos, ypos)
            dc.set_connection(parent)
            if len(coll.operands) == 0:
                xpos += 175
            for coll in coll.operands[::-1]:
                stack.append((coll, dc))
        canvas = root.get_canvas()
        #zoom so that the whole structure is visible
        x1,y1,x2,y2 = canvas.get_bounds()
        alloc = canvas.get_allocation()
        try:
            zoom = min( alloc.width*1.0/(x2 - x1), alloc.height*1.0/(y2 - y1))
        except ZeroDivisionError:
            print(x1, y1, x2, y2)
            zoom = 1
        zoom = min(zoom, 1)
        self.zoomspin.set_value(zoom)



    def draw_coll(self, collection, root, xpos, ypos):
        """
        Create the item on the canvas according to the xmms2 collection type
        """
        if isinstance(collection, ec.Complement):
            treeitem_class = NotOperator
        elif isinstance(collection, ec.Intersection):
            treeitem_class = AndOperator
        elif isinstance(collection, ec.Union):
            treeitem_class = OrOperator
        elif isinstance(collection, ec.Universe): 
            #universe should be before reference
            treeitem_class = UniverseOperator
        elif isinstance(collection, ec.Reference):
            treeitem_class = ReferenceOperator
        elif isinstance(collection, ec.Equals):
            treeitem_class = EqualsOperator
        elif isinstance(collection, ec.Match):
            treeitem_class = MatchOperator
        elif isinstance(collection, ec.Smaller):
            treeitem_class = SmallerOperator
        elif isinstance(collection, ec.Greater):
            treeitem_class = GreaterOperator
        elif isinstance(collection, ec.Has):
            treeitem_class = HasOperator
        elif isinstance(collection, ec.IDList):
            treeitem_class = IDListOperator
        elif isinstance(collection, ec.Queue):
            treeitem_class = QueueOperator
        elif isinstance(collection, ec.PShuffle):
            treeitem_class = PShuffleOperator
        treeitem = treeitem_class(self, root, xpos, ypos)
        #set attributes to collections, which require these
        if treeitem_class in (ReferenceOperator, EqualsOperator, MatchOperator,
                SmallerOperator, GreaterOperator, HasOperator, IDListOperator,
                QueueOperator, PShuffleOperator):
            treeitem.op_attributes.update(collection.attributes)
            treeitem.set_attributes()
        #set ids for medialists
        if treeitem_class in (IDListOperator, QueueOperator, PShuffleOperator):
            treeitem.op_ids = []
            [treeitem.op_ids.append(x) for x in collection.ids]
        return treeitem
        

    def focus_changed(self, item):
        """
        changed the focused item
        """
        child = self.detail_viewport.get_child()
        try:
            self.__focused.set_focused(False)#unfocus the previous one
            self.__focused = None
        except AttributeError:
            pass
        if child != None:
            self.detail_viewport.remove(child)
        if item == None:
            self.active_label.set_markup("")
            self.detail_viewport.add(self.helpframe)
        else:
            frame = item.get_properties_frame()
            self.detail_viewport.add(frame)
            name = '<span size="larger"><b>'+item.op_name+'</b></span>'
            self.active_label.set_markup(name)
            #raise
            item.raise_(None)
            item.set_focused(True)
            self.__focused = item
            self.notebook.set_current_page(1)
        if (isinstance(item, QueueOperator) 
                or isinstance(item, PShuffleOperator)
                or isinstance(item, IDListOperator)):
            self.detail_viewport.set_property('yscale', 1.0)
        else:
            self.detail_viewport.set_property('yscale', 0.0)

    def add_arrow(self, parent, child):
        """
        Add an arrow pointing from the parent to the child
        little math involved, who doesn't like math consider this part magic :P
        """
        pb = parent.get_bounds()
        pm = parent.get_transform()
        pw = pb.x2-pb.x1
        ph = pb.y2-pb.y1
        try:
            pk = ph*1.0/pw
        except ZeroDivisionError:
            pk = float('infinity')*ph


        parent_center = ( parent.get_property('x')+pm[4]+pw/2, 
                parent.get_property('y')+pm[5]+ph/2)


        cb = child.get_bounds()
        cm = child.get_transform()
        cw = cb.x2-cb.x1
        ch = cb.y2-cb.y1
        try:
            ck = ch*1.0/cw
        except ZeroDivisionError:
            ck = float('infinity')*ch
        child_center = ( child.get_property('x')+cm[4]+cw/2, 
                child.get_property('y')+ cm[5] + ch/2)

        dx = parent_center[0] - child_center[0]
        dy = parent_center[1] - child_center[1]
        try:
            dk = dy*1.0/dx
        except ZeroDivisionError:
            dk = float('infinity')*dy

        parent_edge = ''
        child_edge = ''
        if dx < 0: # child is left of parent,parent edges: _^> 
            if dy <= 0: # child is above parent,parent edges: _>
                if pk > dk:
                    parent_edge = 'right'
                else:
                    parent_edge = 'bottom'
                if ck > dk:
                    child_edge = 'left'
                else:
                    child_edge = 'top'
            else: #child is below parent, parent: ^>
                if pk > -dk:
                    parent_edge = 'right'
                else:
                    parent_edge = 'top'
                if ck > -dk:
                    child_edge = 'left'
                else:
                    child_edge = 'bottom'
        else: # parent edges <_^
            if dy <= 0: #parent edges <_
                if pk > -dk:
                    parent_edge = 'left'
                else:
                    parent_edge = 'bottom'
                if ck > -dk:
                    child_edge = 'right'
                else:
                    child_edge = 'top'
            else: #parent edges <^
                if pk > dk:
                    parent_edge = 'left'
                else:
                    parent_edge = 'top'
                if ck > dk:
                    child_edge = 'right'
                else:
                    child_edge = 'bottom'

        if parent_edge == 'top' or parent_edge == 'bottom':
            if parent_edge == 'top':
                py = pm[5]
            else:
                py = pm[5]+ph
            if dk == float('infinity') or dk == float('-infinity'):
                px = parent_center[0]
            else:
                px = (py-parent_center[1])/dk + parent_center[0]
        else: #left, right
            if parent_edge == 'left':
                px = pm[4]
            else:
                px = pm[4]+pw
            if dk == 0.0 or dk == float('nan'):
                py = parent_center[1]
            else:
                py = parent_center[1]+ dk*px- dk*parent_center[0]

        if child_edge == 'top' or child_edge == 'bottom':
            if child_edge == 'top':
                cy = cm[5]
            else:
                cy = cm[5]+ch
            if (dk == float('infinity')) or (dk == float('-infinity')):
                cx = child_center[0]
            else:
                cx = (cy-child_center[1])/dk + child_center[0]
        else: #left, right
            if child_edge == 'left':
                cx = cm[4]
            else:
                cx = cm[4]+cw
            if dk == 0.0 or dk == float('nan'):
                cy = child_center[1]
            else:
                cy = child_center[1]+ dk*cx- dk*child_center[0]
        
        t_points = goo.Points([(cx, cy), (px, py)])
        if (parent, child) in self.arrows: #update the positions  
            pline = self.arrows[(parent, child)]
            pline.set_property('points', t_points)
            pline.request_update()
        else: #create a new one
            pline = goo.Polyline(parent = self.root,
                    points = t_points,
                    stroke_color_rgba = 0x10101080,
                    fill_color_rgba = 0x00000000,
                    end_arrow = True,
                    line_width = 3.0,
                    arrow_length = 5,
                    arrow_width = 5)
            self.arrows[(parent, child)] = pline
            pline.lower(None)
            pline.connect('button-press-event', self.remove_arrow_event, 
                    (parent, child))

    def remove_arrow_event(self, item, target, event, key):
        """if an arrow was clicked with shift pressed down delete it"""
        if (event.button == 1) and (event.state & gtk.gdk.SHIFT_MASK):
            self.remove_arrow(key)

    def remove_arrow(self, key):
        """removes the arrow and severes the connection between the items
        key is (parent, child)"""
        if key in self.arrows:
            key[1].unset_connection(key[0])
            self.arrows[key].remove()
            del self.arrows[key]

